#include "LexAnalysis.h"
#include "VariablesTree.h"
#include <math.h>

typedef enum {NOERROR = 0, NONUMBER = 1, NOBKT = 2, NOEOF = 3, UNKNOWNVAR = 4, NODIFFVAR = 5, NOEOC = 6, NOFRACPART = 7} MERROR;

typedef struct
{
    token* Lexems;
    long length;
    long position;
    MERROR error;
} expression;

tree* GetN(expression* Exp);
tree* GetP(expression* Exp);
tree* GetT(expression* Exp);
tree* GetE(expression* Exp);
tree* GetG(expression* Exp);
tree* GetF(expression* Exp);
tree* diff(tree* this, char var);
tree* tree_cpy(tree* this);
expression* exp_ctor(char* FileName);
int exp_dtor(expression* Exp);
int exp_ok(expression* Exp);
int MathDump(tree* this, char* here);
int NodeDump(tree* this, FILE* here);
int GetError(expression* Exp);
int opt1(tree* this);
int opt2(tree* this);
int opt(tree* this);

int main()
{
    char FileName[100];
    scanf("%100s", FileName);
    expression* Exp = exp_ctor(FileName);
    tree* TreeExp = GetG(Exp);
    exp_dtor(Exp);
    opt(TreeExp);
    scanf("%100s", FileName);
    MathDump(TreeExp, FileName);
    return 0;
}


tree* GetN(expression* Exp)
{
    if (Exp -> Lexems[Exp -> position].type != NUMBER && Exp -> Lexems[Exp -> position].type != VAR)
    {
        Exp -> error = NONUMBER;
        return NULL;
    }
    else if (Exp -> Lexems[Exp -> position].type == NUMBER)
    {
        if (Exp -> Lexems[Exp -> position + 1].type != DOT)
        {
            data_t x;
            x.type = 'n';
            x.number = Exp -> Lexems[Exp -> position].number;
            Exp -> position++;
            return tree_ctor(x);
        }
        else
        {
            double integ = Exp -> Lexems[Exp -> position].number;
            Exp -> position += 2;
            if (Exp -> Lexems[Exp -> position].type != NUMBER)
            {
                Exp -> error = NOFRACPART;
                return NULL;
            }
            else
            {
                double frac = Exp -> Lexems[Exp -> position].number;
                while (frac >= 1) frac = frac/10;
                data_t x;
                x.type = 'n';
                x.number = integ + frac;
                Exp -> position++;
                return tree_ctor(x);
            }
        }
    }
    else
    {
        if ('a' <= Exp -> Lexems[Exp -> position].var &&
            Exp -> Lexems[Exp -> position].var <= 'd')
        {
            data_t x;
            x.type = 'v';
            x.var = Exp -> Lexems[Exp -> position].var;
            Exp -> position++;
            return tree_ctor(x);
        }
        else
        {
            Exp -> error = UNKNOWNVAR;
            return NULL;
        }
    }
}

tree* GetP(expression* Exp)
{
    if (Exp -> error) return NULL;
    if (Exp -> Lexems[Exp -> position].type == BR && Exp -> Lexems[Exp -> position].br == '(')
    {
        Exp -> position++;
        tree* result = GetE(Exp);
        if (Exp -> error) return NULL;

        if (Exp -> Lexems[Exp -> position].type == BR && Exp -> Lexems[Exp -> position].br == ')')
        {
            Exp -> position++;

            if (Exp -> Lexems[Exp -> position].type == OP && Exp -> Lexems[Exp -> position].op == '\'')
            {
                Exp -> position++;
                if (Exp -> Lexems[Exp -> position].type != VAR)
                {
                    Exp -> error = NODIFFVAR;
                    return NULL;
                }
                else
                {
                    tree* new_result = diff(result, Exp -> Lexems[Exp -> position].var);
                    tree_dtor(result);
                    Exp -> position++;
                    return new_result;
                }
            }
            else return result;
        }
        else
        {
            Exp -> error = NOBKT;
            return NULL;
        }
    }
    else return GetN(Exp);
}

tree* GetT(expression* Exp)
{
    if (Exp -> error) return NULL;
    tree* result = GetF(Exp);
    if (Exp -> error) return NULL;
    while (Exp -> Lexems[Exp -> position].type == OP &&
           (Exp -> Lexems[Exp -> position].op == '*' || Exp -> Lexems[Exp -> position].op == '/'))
    {
        data_t x;
        x.type = 'o';
        x.op = Exp -> Lexems[Exp -> position].op;

        Exp -> position++;
        tree* new_result = tree_ctor(x);
        add_node(new_result, result, 'l');
        tree* NewNumber = GetF(Exp);
        if (Exp -> error) return NULL;
        add_node(new_result, NewNumber, 'r');
        result = new_result;
    }
    return result;
}

tree* GetE(expression* Exp)
{
    if (Exp -> error) return NULL;
    tree* result = GetT(Exp);
    if (Exp -> error) return NULL;
    while (Exp -> Lexems[Exp -> position].type == OP &&
           (Exp -> Lexems[Exp -> position].op == '+' || Exp -> Lexems[Exp -> position].op == '-'))
    {
        data_t x;
        x.type = 'o';
        x.op = Exp -> Lexems[Exp -> position].op;
        Exp -> position++;
        tree* new_result = tree_ctor(x);
        add_node(new_result, result, 'l');
        tree* NewNumber = GetT(Exp);
        if (Exp -> error) return NULL;
        add_node(new_result, NewNumber, 'r');
        result = new_result;
    }
    return result;
}

tree* GetG(expression* Exp)
{
    if (!exp_ok(Exp)) return NULL;
    Exp -> error = NOERROR;
    Exp -> position = 0;
    tree* expression = GetE(Exp);
    if (Exp -> error) return NULL;
    if (Exp -> Lexems[Exp -> position].type == EOC) return expression;
    else
    {
        Exp -> error = NOEOC;
        return NULL;
    }
}

int GetError(expression* Exp)
{
    return Exp -> error;
}

tree* GetF(expression* Exp)
{
    if (Exp -> error) return NULL;
    if (Exp -> Lexems[Exp -> position].type == FUNC)
    {
        data_t x;
        x.type = 'f';
        x.func = Exp -> Lexems[Exp -> position].func;
        tree* result = tree_ctor(x);
        Exp -> position++;
        tree* Arg = GetP(Exp);
        add_node(result, Arg, 'l');
        return result;
    }
    else return GetP(Exp);
}

tree* diff(tree* this, char var)
{
    if (!tree_ok(this)) return NULL;
    if (this -> data.type == 'n')
    {
        data_t zero;
        zero.type = 'n';
        zero.number = 0;
        return tree_ctor(zero);
    }

    if (this -> data.type == 'v')
    {
        if (var == this -> data.var)
        {
            data_t one;
            one.type = 'n';
            one.number = 1;
            return tree_ctor(one);
        }
        else
        {
            data_t zero;
            zero.type = 'n';
            zero.number = 0;
            return tree_ctor(zero);
        }
    }

    if (this -> data.type == 'f')
    {
        data_t mul;
        mul.type = 'o';
        mul.op = '*';
        tree* result = tree_ctor(mul);
        add_node(result, diff(this -> left, var), 'r');
        if (this -> data.func == SIN)
        {
            data_t fcos;
            fcos.type = 'f';
            fcos.func = COS;
            tree* left = tree_ctor(fcos);
            add_node(left, tree_cpy(this -> left), 'l');
            add_node(result, left, 'l');
        }
        if (this -> data.func == COS)
        {
            data_t fsin;
            fsin.type = 'f';
            fsin.func = SIN;
            tree* leftleft = tree_ctor(fsin);

            add_node(leftleft, tree_cpy(this -> left), 'l');

            tree* left = tree_ctor(mul);

            data_t minusone;
            minusone.type = 'n';
            minusone.number = -1;
            tree* minustree = tree_ctor(minusone);

            add_node(left, minustree, 'r');
            add_node(left, leftleft, 'l');

            add_node(result, left, 'l');
        }
        return result;
    }

    if (this -> data.type == 'o')
    {
        if (this -> data.op == '+' || this -> data.op == '-')
        {
            data_t op;
            op.type = 'o';
            op.op = this -> data.op;
            tree* result = tree_ctor(op);

            add_node(result, diff(this -> left, var), 'l');
            add_node(result, diff(this -> right, var), 'r');
            return result;
        }
        if (this -> data.op == '*')
        {
            data_t add;
            add.type = 'o';
            add.op = '+';
            tree* result = tree_ctor(add);

            data_t mul;
            mul.type = 'o';
            mul.op = '*';
            tree* left = tree_ctor(mul);
            tree* right = tree_ctor(mul);

            add_node(left, diff(this -> left, var), 'l');
            add_node(left, tree_cpy(this -> right), 'r');
            add_node(right, diff(this -> right, var), 'l');
            add_node(right, tree_cpy(this -> left), 'r');

            add_node(result, left, 'l');
            add_node(result, right, 'r');
            return result;
        }
        if (this -> data.op == '/')
        {
            data_t div;
            div.type = 'o';
            div.op = '/';
            tree* result = tree_ctor(div);

            data_t sub;
            sub.type = 'o';
            sub.op = '-';
            tree* nominator = tree_ctor(sub);

            data_t mul;
            mul.type = 'o';
            mul.op = '*';

            tree* member1 = tree_ctor(mul);
            add_node(member1, diff(this -> left, var), 'l');
            add_node(member1, tree_cpy(this -> right), 'r');
            add_node(nominator, member1, 'l');

            tree* member2 = tree_ctor(mul);
            add_node(member2, diff(this -> right, var), 'l');
            add_node(member2, tree_cpy(this -> left), 'r');
            add_node(nominator, member2, 'r');

            tree* denominator = tree_ctor(mul);
            add_node(denominator, tree_cpy(this -> right), 'l');
            add_node(denominator, tree_cpy(this -> right), 'r');

            add_node(result, nominator, 'l');
            add_node(result, denominator, 'r');

            return result;
        }
    }
    return NULL;
}

tree* tree_cpy(tree* this)
{
    if (!tree_ok(this)) return NULL;
    tree* result = tree_ctor(this -> data);
    if (this -> right) add_node(result, tree_cpy(this -> right), 'r');
    if (this -> left)  add_node(result, tree_cpy(this -> left ), 'l');
    return result;
}

expression* exp_ctor(char* FileName)
{
    if (!FileName) return NULL;
    expression* result = calloc(1, sizeof(expression));
    if (!result) return NULL;
    char* text = FileToArray(FileName);
//return NULL;
    result -> Lexems = LexAn(text);
//return NULL;
    if (!result -> Lexems) return NULL;
    result -> length = 0;
    while(result -> Lexems[result -> length].type != EOC)
    {
        result -> length++;
    }
    result -> position = 0;
    result -> error = NOERROR;
    return result;
}

int exp_dtor(expression* Exp)
{
    if (!exp_ok(Exp)) return 0;
    free(Exp -> Lexems);
    Exp -> Lexems = NULL;
    Exp -> length = 0;
    Exp -> position = 0;
    Exp -> error = NOERROR;
    free(Exp);
    Exp = NULL;
    return 1;
}

int exp_ok(expression* Exp)
{
    if (!Exp) return 0;
    if (!Exp -> Lexems) return 0;
    if (Exp -> position > Exp -> length) return 0;
    return 1;
}

int MathDump(tree* this, char* here)
{
    FILE* dest = fopen(here, "w");
    if (!dest) return 0;
    if (!tree_ok(this)) return 0;
    int success = NodeDump(this, dest);
    fclose(dest);
    return success;
}

int NodeDump(tree* this, FILE* here)
{
    if (!here) return 0;
    if (!tree_ok(this)) return 0;
    if (this -> data.type == 'o')
    {
        int success1 = NodeDump(this -> left, here);
        int success2 = NodeDump(this -> right, here);
        if (this -> data.op == '+') fprintf(here, "add\n");
        if (this -> data.op == '-') fprintf(here, "sub\n");
        if (this -> data.op == '*') fprintf(here, "mul\n");
        if (this -> data.op == '/') fprintf(here, "div\n");
        return success1 * success2;
    }
    if (this -> data.type == 'n')
    {
        fprintf(here, "push %lg\n", this -> data.number);
        return 1;
    }
    if (this -> data.type == 'f')
    {
        int success = NodeDump(this -> left, here);
        if (this -> data.func == SIN) fprintf(here, "sin\n");
        if (this -> data.func == COS) fprintf(here, "cos\n");
        return success;
    }
    if (this -> data.type == 'v')
    {
        fprintf(here, "push %cx\n", this -> data.var);
        return 1;
    }
    return 0;
}

int opt1(tree* this)
{
    if (this -> data.type == 'n') return 0;
    if (this -> data.type == 'o')
    {
        int success1 = opt1(this -> left);
        int success2 = opt1(this -> right);
        if (this -> left -> data.type == 'n' && this -> right -> data.type == 'n')
        {
            if (this -> data.op == '+')
            {
                double a = this -> left -> data.number;
                double b = this -> right -> data.number;
                tree_dtor(this -> right);
                tree_dtor(this -> left);
                this -> right = NULL;
                this -> left = NULL;
                this -> data.type = 'n';
                this -> data.number = a + b;
                return 1;
            }
            if (this -> data.op == '-')
            {
                double a = this -> left -> data.number;
                double b = this -> right -> data.number;
                tree_dtor(this -> left);
                tree_dtor(this -> right);
                this -> left = NULL;
                this -> right = NULL;
                this -> data.type = 'n';
                this -> data.number = a - b;
                return 1;
            }
            if (this -> data.op == '*')
            {
                double a = this -> left -> data.number;
                double b = this -> right -> data.number;
                tree_dtor(this -> left);
                tree_dtor(this -> right);
                this -> left = NULL;
                this -> right = NULL;
                this -> data.type = 'n';
                this -> data.number = a*b;
                return 1;
            }
            if (this -> data.op == '/')
            {
                double a = this -> left -> data.number;
                double b = this -> right -> data.number;
                if (b == 0) return success1*success2;
                tree_dtor(this -> left);
                tree_dtor(this -> right);
                this -> right = NULL;
                this -> left = NULL;
                this -> data.type = 'n';
                this -> data.number = a/b;
                return 1;
            }
        }
        else return success1*success2;
    }
    if (this -> data.type == 'v') return 0;
    if (this -> data.type == 'f')
    {
        int success1 = opt1(this -> left);
        if (this -> left -> data.type == 'n')
        {
            double a = this -> left -> data.number;
            tree_dtor(this -> left);
            this -> left = NULL;
            if (this -> data.func == SIN)
            {
                a = sin(a);
            }
            if (this -> data.func == COS)
            {
                a = cos(a);
            }
            this -> data.type = 'n';
            this -> data.number = a;
            return 1;
        }
        else return success1;
    }
    return 0;
}

int opt2(tree* this)
{
    if (this -> data.type == 'n')
    {
        return 0;
    }
    if (this -> data.type == 'o')
    {
        int success1 = opt2(this -> left);
        int success2 = opt2(this -> right);
        if (this -> data.op == '+')
        {
            if (this -> left -> data.type == 'n' && this -> left -> data.number == 0 && this -> right -> data.type == 'v')
            {
                tree_dtor(this -> left);
                this -> left = NULL;
                this -> data.type = 'v';
                this -> data.var = this -> right -> data.var;
                tree_dtor(this -> right);
                this -> right = NULL;
                return 1;
            }
            if (this -> right -> data.type == 'n' && this -> right -> data.number == 0 && this -> left -> data.type == 'v')
            {
                tree_dtor(this -> right);
                this -> right = NULL;
                this -> data.type = 'v';
                this -> data.var = this -> left -> data.var;
                tree_dtor(this -> left);
                this -> left = NULL;
                return 1;
            }
        }
        if (this -> data.op == '-')
        {
            if (this -> left -> data.type == 'v' && this -> right -> data.type == 'n' &&
                this -> right -> data.number == 0)
            {
                tree_dtor(this -> right);
                this -> right = NULL;
                this -> data.type = 'v';
                this -> data.var = this -> left -> data.var;
                tree_dtor(this -> left);
                this -> left = NULL;
                return 1;
            }
        }
        if (this -> data.op == '*')
        {
            if ((this -> left -> data.type == 'n' && this -> left -> data.number == 0 &&
                (this -> right -> data.type == 'n'|| this -> right -> data.type == 'v')) ||
                (this -> right -> data.type == 'n' && this -> right -> data.number == 0 &&
                (this -> left -> data.type == 'n'|| this -> left -> data.type == 'v')))
            {
                tree_dtor(this -> left);
                tree_dtor(this -> right);
                this -> right = NULL;
                this -> left = NULL;
                this -> data.type = 'n';
                this -> data.number = 0;
                return 1;
            }
            if (this -> left -> data.type == 'n' && this -> left -> data.number == 1 &&
                (this -> right -> data.type == 'v' || this -> right -> data.type == 'n'))
            {
                tree_dtor(this -> left);
                this -> left = NULL;
                this -> data.type = this -> right -> data.type;
                if (this -> data.type == 'v') this -> data.var = this -> right -> data.var;
                if (this -> data.type == 'n') this -> data.number = this -> right -> data.number;
                tree_dtor(this -> right);
                this -> right = NULL;
                return 1;
            }
            if (this -> right -> data.type == 'n' && this -> right -> data.number == 1 &&
                (this -> left -> data.type == 'v' || this -> left -> data.type == 'n'))
            {
                tree_dtor(this -> right);
                this -> right = NULL;
                this -> data.type = this -> left -> data.type;
                if (this -> data.type == 'v') this -> data.var = this -> left -> data.var;
                if (this -> data.type == 'n') this -> data.number = this -> left -> data.number;
                tree_dtor(this -> left);
                this -> left = NULL;
                return 1;
            }
            if (this -> left -> data.type == 'n' && this -> left -> data.number == 1 &&
                (this -> right -> data.type == 'v' || this -> right -> data.type == 'n'))
            {
                tree_dtor(this -> left);
                this -> left = NULL;
                this -> data.type = this -> right -> data.type;
                if (this -> data.type == 'v') this -> data.var = this -> right -> data.var;
                if (this -> data.type == 'n') this -> data.number = this -> right -> data.number;
                tree_dtor(this -> right);
                this -> right = NULL;
                return 1;
            }
        }
        if (this -> data.op == '/')
        {
            if (this -> left -> data.type == 'n' && this -> left -> data.number == 1 &&
                (this -> right -> data.type == 'v' || this -> right -> data.type == 'n'))
            {
                tree_dtor(this -> left);
                this -> left = NULL;
                this -> data.type = this -> right -> data.type;
                if (this -> data.type == 'v') this -> data.var = this -> right -> data.var;
                if (this -> data.type == 'n') this -> data.number = this -> right -> data.number;
                tree_dtor(this -> right);
                this -> right = NULL;
                return 1;
            }
        }
        else return success1*success2;
    }
    if (this -> data.type == 'f')
    {
        if (this -> data.func == COS)
        {
            return 0;
        }
        if (this -> data.func == SIN)
        {
            return 0;
        }
    }
    if (this -> data.type == 'v')
    {
        return 0;
    }
    return 0;
}

int opt(tree* this)
{
    if (!tree_ok(this)) return 0;
    int success1 = 1, success2 = 1;
    while(success1 + success2)
    {
        success1 = opt1(this);
        success2 = opt2(this);
    }
    return 1;
}

